package com.dc.sr.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 使用redis setnx指令实现分布式锁
 *
 * @author duchao
 */
@Slf4j
@Component
public class RedisLock {

    private final StringRedisTemplate redisTemplate;

    public RedisLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 加锁(最简单的锁，不带多次尝试获取锁)
     * <p>
     * setIfAbsent对应redis的setNx指令，设置值和过期时间是原子性操作
     *
     * @param key        键
     * @param expireTime key的过期时间
     */
    public Boolean lock(String key, Long expireTime) {
        if (key == null) {
            return false;
        }

        return redisTemplate.opsForValue().setIfAbsent(key, UUID.randomUUID().toString(), expireTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 加锁（带等待时间内多次尝试）
     *
     * @param key          键
     * @param waitTime     等待时间,在这个时间内会多次尝试获取锁，超过这个时间还没获得锁，就返回false
     * @param intervalTime 间隔时间,每隔多长时间尝试一次获的锁
     * @param expireTime   key的过期时间
     */
    public Boolean lock(String key, Long waitTime, Long intervalTime, Long expireTime) {
        if (key == null) {
            return false;
        }

        String value = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);

        if (lock) {
            return true;
        } else {
            long startTime = System.currentTimeMillis();
            long closeTime = startTime + waitTime;

            while (System.currentTimeMillis() < closeTime) {
                lock = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
                if (lock) {
                    return true;
                }

                try {
                    Thread.sleep(intervalTime);
                } catch (InterruptedException e) {
                    log.error("获取锁异常", e);
                }
            }

        }

        return false;
    }

    /**
     * 加锁（拿不到锁就一直尝试）
     *
     * @param key          键
     * @param intervalTime 间隔时间,每隔多长时间尝试一次获的锁
     * @param expireTime   key的过期时间
     */
    public Boolean lock(String key, Long intervalTime, Long expireTime) {
        if (key == null) {
            return false;
        }

        String value = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);

        if (lock) {
            return true;
        }
        while (true) {
            lock = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
            if (lock) {
                return true;
            }

            try {
                Thread.sleep(intervalTime);
            } catch (InterruptedException e) {
                log.error("获取锁异常", e);
            }
        }
    }

    /**
     * 释放锁
     *
     * @param key 键
     */
    public void unLock(String key) {
        if (key != null) {
            redisTemplate.delete(key);
        }
    }
}
